// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StyleCopFileScavenger.cs" company="StyleCop for ReSharper Development Team">
//   Copyright (c) StyleCop for ReSharper Development Team. All rights reserved.
// </copyright>
// <summary>
//   Defines the StyleCopFileScavenger type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace StyleCop.For.ReSharper
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;

    using JetBrains.ProjectModel;
    using JetBrains.ReSharper.Psi;

    #endregion

    /// <summary>
    /// Manages the scaveging of temporary files used by SCfR#.
    /// </summary>
    public class StyleCopFileScavenger
    {
        #region Constants and Fields

        /// <summary>
        /// Folder path where Shadow Folders / Files are kept.
        /// </summary>
        private const string ShadowCopyFolderPath = @"StyleCopForReSharper";

        /// <summary>
        /// Timeout (in seconds) that we should clean down the Shadow Copy folders and files.
        /// </summary>
        private const int ShadowCopyScavengeTimoutPeriod = 60;

        /// <summary>
        /// Object that can be used to synchronize access to the Shadow Files.
        /// </summary>
        private static readonly object syncRoot = new object();

        /// <summary>
        /// Used to count down to the next shadow file clean up. 
        /// </summary>
        private readonly Stopwatch scavengingStopwatch;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the StyleCopFileScavenger class.
        /// </summary>
        /// <param name="scavengingStopwatch">
        /// Global static stopwatch which regulates scavenging.
        /// </param>
        public StyleCopFileScavenger(Stopwatch scavengingStopwatch)
        {
            this.scavengingStopwatch = scavengingStopwatch;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a shadow copy of the file currently being proccessed within the IDE.
        /// </summary>
        /// <remarks>
        /// A shadow copy is taken to keep the IDE highlights in synch with the violations encountered,
        /// as Microsoft StyleCop only executes against a file, the shadow copy is required.
        /// </remarks>
        /// <param name="file">
        /// File to analyse.
        /// </param>
        /// <returns>
        /// A List of the shadow copied files.
        /// </returns>
        public List<string> CreateShadowCopy(IProjectFile file)
        {
            var shadowCopyList = new List<string>();

            lock (syncRoot)
            {
                try
                {
                    var files = GetAllFilesForFile(file);

                    shadowCopyList = new List<string>(files.Count);

                    foreach (var projectFile in files)
                    {
                        var fileInfo = new FileInfo(GetShadowFilePathForFile(projectFile));

                        if (!fileInfo.Exists)
                        {
                            if (fileInfo.Directory != null)
                            {
                                fileInfo.Directory.Create();
                            }
                        }

                        var shadowFilePath = fileInfo.ToString();

                        var psiFile = PsiManager.GetInstance(file.GetSolution()).GetPsiFile(projectFile);
                        File.WriteAllText(shadowFilePath, psiFile.GetText());
                        shadowCopyList.Add(shadowFilePath);
                    }
                }
                catch (IOException exception)
                {
                    JetBrains.Util.Logger.LogException(exception);
                }
                catch (UnauthorizedAccessException exception)
                {
                    JetBrains.Util.Logger.LogException(exception);
                }
            }

            return shadowCopyList;
        }

        /// <summary>
        /// Periodically clears down the Shadow Folders and temporary files.
        /// </summary>
        public void ScavengeShadowCopies()
        {
            // Only clean up the cache every 30 seconds or so.
            if (this.scavengingStopwatch.Elapsed > new TimeSpan(0, 0, 0, ShadowCopyScavengeTimoutPeriod))
            {
                DeleteShadowCopy();

                this.scavengingStopwatch.Reset();
                this.scavengingStopwatch.Start();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Deletes the shadow copy files and folder structure.
        /// </summary>
        private static void DeleteShadowCopy()
        {
            lock (syncRoot)
            {
                var directoryInfo = new DirectoryInfo(GetShadowFolderPath());

                if (directoryInfo.Exists)
                {
                    try
                    {
                        // remove all files
                        foreach (var file in directoryInfo.GetFiles())
                        {
                            file.Delete();
                        }

                        // then remove the directory
                        directoryInfo.Delete(true);
                    }
                    catch (IOException)
                    {
                        return;
                    }
                    catch (UnauthorizedAccessException)
                    {
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// The passed in file is always index 0 in the returned IList.
        /// </summary>
        /// <param name="projectFile"></param>
        /// <returns></returns>
        private static IList<IProjectFile> GetAllFilesForFile(IProjectFile projectFile)
        {
            var rootDependsItem = projectFile.GetDependsUponItemForItem();

            if (rootDependsItem == null)
            {
                return new List<IProjectFile> { projectFile };
            }

            var dependentFiles = rootDependsItem.GetDependentFiles();

            var list = new List<IProjectFile> { projectFile };

            foreach (var file in dependentFiles)
            {
                if (!file.Equals(projectFile))
                {
                    list.Add(file);
                }
            }

            return list;
        }

        /// <summary>
        /// IsGenerated the Shadow Copy file path.
        /// </summary>
        /// <param name="projectFile">
        /// File to create a shadow copy of.
        /// </param>
        /// <returns>
        /// Shadow Copy file path.
        /// </returns>
        private static string GetShadowFilePathForFile(IProjectFile projectFile)
        {
            var realFilePath = projectFile.Location.FullPath.Remove(0, 3);

            return Path.Combine(GetShadowFolderPath(), realFilePath);
        }

        /// <summary>
        /// The path to the Shadow Folder Path.
        /// </summary>
        /// <returns>
        /// Shadow Folder Path.
        /// </returns>
        private static string GetShadowFolderPath()
        {
            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ShadowCopyFolderPath);
        }

        #endregion
    }
}